package org.ksoap2.serialization;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.SoapFault;
import org.ksoap2.SoapFault12;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

public class SoapSerializationEnvelope extends SoapEnvelope
{
  protected static final int QNAME_TYPE = 1;
  protected static final int QNAME_NAMESPACE = 0;
  protected static final int QNAME_MARSHAL = 3;
  private static final String ANY_TYPE_LABEL = "anyType";
  private static final String ARRAY_MAPPING_NAME = "Array";
  private static final String NULL_LABEL = "null";
  private static final String NIL_LABEL = "nil";
  private static final String HREF_LABEL = "href";
  private static final String ID_LABEL = "id";
  private static final String ROOT_LABEL = "root";
  private static final String TYPE_LABEL = "type";
  private static final String ITEM_LABEL = "item";
  private static final String ARRAY_TYPE_LABEL = "arrayType";
  static final Marshal DEFAULT_MARSHAL = new DM();
  public Hashtable properties = new Hashtable();

  Hashtable idMap = new Hashtable();
  Vector multiRef;
  public boolean implicitTypes;
  public boolean dotNet;
  public boolean avoidExceptionForUnknownProperty;
  protected Hashtable qNameToClass = new Hashtable();

  protected Hashtable classToQName = new Hashtable();

  protected boolean addAdornments = true;

  public SoapSerializationEnvelope(int version)
  {
    super(version);
    addMapping(this.enc, "Array", PropertyInfo.VECTOR_CLASS);
    DEFAULT_MARSHAL.register(this);
  }

  public boolean isAddAdornments()
  {
    return this.addAdornments;
  }

  public void setAddAdornments(boolean addAdornments)
  {
    this.addAdornments = addAdornments;
  }

  public void setBodyOutEmpty(boolean emptyBody)
  {
    if (emptyBody)
      this.bodyOut = null;
  }

  public void parseBody(XmlPullParser parser)
    throws IOException, XmlPullParserException
  {
    this.bodyIn = null;
    parser.nextTag();
    if ((parser.getEventType() == 2) && (parser.getNamespace().equals(this.env)) && (parser.getName().equals("Fault")))
    {
      SoapFault fault;
      if (this.version < 120)
        fault = new SoapFault(this.version);
      else {
        fault = new SoapFault12(this.version);
      }
      fault.parse(parser);
      this.bodyIn = fault;
    } else {
      while (parser.getEventType() == 2) {
        String rootAttr = parser.getAttributeValue(this.enc, "root");

        Object o = read(parser, null, -1, parser.getNamespace(), parser.getName(), PropertyInfo.OBJECT_TYPE);

        if (("1".equals(rootAttr)) || (this.bodyIn == null)) {
          this.bodyIn = o;
        }
        parser.nextTag();
      }
    }
  }

  protected void readSerializable(XmlPullParser parser, SoapObject obj)
    throws IOException, XmlPullParserException
  {
    for (int counter = 0; counter < parser.getAttributeCount(); counter++) {
      String attributeName = parser.getAttributeName(counter);
      String value = parser.getAttributeValue(counter);
      obj.addAttribute(attributeName, value);
    }
    readSerializable(parser, obj);
  }

  protected void readSerializable(XmlPullParser parser, KvmSerializable obj)
    throws IOException, XmlPullParserException
  {
    while (parser.nextTag() != 3) {
      String name = parser.getName();
      if ((!this.implicitTypes) || (!(obj instanceof SoapObject))) {
        PropertyInfo info = new PropertyInfo();
        int propertyCount = obj.getPropertyCount();
        boolean propertyFound = false;

        for (int i = 0; (i < propertyCount) && (!propertyFound); i++) {
          info.clear();
          obj.getPropertyInfo(i, this.properties, info);

          if (((!name.equals(info.name)) || (info.namespace != null)) && ((!name.equals(info.name)) || (!parser.getNamespace().equals(info.namespace))))
            continue;
          propertyFound = true;
          obj.setProperty(i, read(parser, obj, i, null, null, info));
        }

        if (!propertyFound) {
          if (this.avoidExceptionForUnknownProperty)
            while (true)
              if (parser.next() == 3) { if (name.equals(parser.getName())) break; continue;
              }
          throw new RuntimeException("Unknown Property: " + name);
        }

      }
      else
      {
        ((SoapObject)obj).addProperty(parser.getName(), read(parser, obj, obj.getPropertyCount(), ((SoapObject)obj).getNamespace(), name, PropertyInfo.OBJECT_TYPE));
      }
    }

    parser.require(3, null, null);
  }

  protected Object readUnknown(XmlPullParser parser, String typeNamespace, String typeName)
    throws IOException, XmlPullParserException
  {
    String name = parser.getName();
    String namespace = parser.getNamespace();

    Vector attributeInfoVector = new Vector();
    for (int attributeCount = 0; attributeCount < parser.getAttributeCount(); attributeCount++) {
      AttributeInfo attributeInfo = new AttributeInfo();
      attributeInfo.setName(parser.getAttributeName(attributeCount));
      attributeInfo.setValue(parser.getAttributeValue(attributeCount));
      attributeInfo.setNamespace(parser.getAttributeNamespace(attributeCount));
      attributeInfo.setType(parser.getAttributeType(attributeCount));
      attributeInfoVector.addElement(attributeInfo);
    }

    parser.next();
    Object result = null;
    String text = null;
    if (parser.getEventType() == 4) {
      text = parser.getText();
      SoapPrimitive sp = new SoapPrimitive(typeNamespace, typeName, text);
      result = sp;

      for (int i = 0; i < attributeInfoVector.size(); i++) {
        sp.addAttribute((AttributeInfo)attributeInfoVector.elementAt(i));
      }
      parser.next();
    } else if (parser.getEventType() == 3) {
      SoapObject so = new SoapObject(typeNamespace, typeName);

      for (int i = 0; i < attributeInfoVector.size(); i++) {
        so.addAttribute((AttributeInfo)attributeInfoVector.elementAt(i));
      }
      result = so;
    }

    if (parser.getEventType() == 2) {
      if ((text != null) && (text.trim().length() != 0)) {
        throw new RuntimeException("Malformed input: Mixed content");
      }
      SoapObject so = new SoapObject(typeNamespace, typeName);

      for (int i = 0; i < attributeInfoVector.size(); i++) {
        so.addAttribute((AttributeInfo)attributeInfoVector.elementAt(i));
      }

      while (parser.getEventType() != 3) {
        so.addProperty(parser.getName(), read(parser, so, so.getPropertyCount(), null, null, PropertyInfo.OBJECT_TYPE));

        parser.nextTag();
      }
      result = so;
    }
    parser.require(3, namespace, name);
    return result;
  }

  private int getIndex(String value, int start, int dflt) {
    if (value == null) {
      return dflt;
    }
    return value.length() - start < 3 ? dflt : Integer.parseInt(value.substring(start + 1, value.length() - 1));
  }

  protected void readVector(XmlPullParser parser, Vector v, PropertyInfo elementType)
    throws IOException, XmlPullParserException
  {
    String namespace = null;
    String name = null;
    int size = v.size();
    boolean dynamic = true;
    String type = parser.getAttributeValue(this.enc, "arrayType");
    if (type != null) {
      int cut0 = type.indexOf(':');
      int cut1 = type.indexOf("[", cut0);
      name = type.substring(cut0 + 1, cut1);
      String prefix = cut0 == -1 ? "" : type.substring(0, cut0);
      namespace = parser.getNamespace(prefix);
      size = getIndex(type, cut1, -1);
      if (size != -1) {
        v.setSize(size);
        dynamic = false;
      }
    }
    if (elementType == null) {
      elementType = PropertyInfo.OBJECT_TYPE;
    }
    parser.nextTag();
    int position = getIndex(parser.getAttributeValue(this.enc, "offset"), 0, 0);
    while (parser.getEventType() != 3)
    {
      position = getIndex(parser.getAttributeValue(this.enc, "position"), 0, position);
      if ((dynamic) && (position >= size)) {
        size = position + 1;
        v.setSize(size);
      }

      v.setElementAt(read(parser, v, position, namespace, name, elementType), position);
      position++;
      parser.nextTag();
    }
    parser.require(3, null, null);
  }

  public Object read(XmlPullParser parser, Object owner, int index, String namespace, String name, PropertyInfo expected)
    throws IOException, XmlPullParserException
  {
    String elementName = parser.getName();
    String href = parser.getAttributeValue(null, "href");
    Object obj;
    if (href != null) {
      if (owner == null) {
        throw new RuntimeException("href at root level?!?");
      }
      href = href.substring(1);
      obj = this.idMap.get(href);
      if ((obj == null) || ((obj instanceof FwdRef))) {
        FwdRef f = new FwdRef();
        f.next = ((FwdRef)obj);
        f.obj = owner;
        f.index = index;
        this.idMap.put(href, f);
        obj = null;
      }
      parser.nextTag();
      parser.require(3, null, elementName);
    } else {
      String nullAttr = parser.getAttributeValue(this.xsi, "nil");
      String id = parser.getAttributeValue(null, "id");
      if (nullAttr == null) {
        nullAttr = parser.getAttributeValue(this.xsi, "null");
      }
      if ((nullAttr != null) && (SoapEnvelope.stringToBoolean(nullAttr))) {
        obj = null;
        parser.nextTag();
        parser.require(3, null, elementName);
      } else {
        String type = parser.getAttributeValue(this.xsi, "type");
        if (type != null) {
          int cut = type.indexOf(':');
          name = type.substring(cut + 1);
          String prefix = cut == -1 ? "" : type.substring(0, cut);
          namespace = parser.getNamespace(prefix);
        } else if ((name == null) && (namespace == null)) {
          if (parser.getAttributeValue(this.enc, "arrayType") != null) {
            namespace = this.enc;
            name = "Array";
          } else {
            Object[] names = getInfo(expected.type, null);
            namespace = (String)names[0];
            name = (String)names[1];
          }
        }

        if (type == null) {
          this.implicitTypes = true;
        }
        obj = readInstance(parser, namespace, name, expected);
        if (obj == null) {
          obj = readUnknown(parser, namespace, name);
        }
      }

      if (id != null) {
        Object hlp = this.idMap.get(id);
        if ((hlp instanceof FwdRef)) {
          FwdRef f = (FwdRef)hlp;
          do {
            if ((f.obj instanceof KvmSerializable))
              ((KvmSerializable)f.obj).setProperty(f.index, obj);
            else {
              ((Vector)f.obj).setElementAt(obj, f.index);
            }
            f = f.next;
          }
          while (f != null);
        } else if (hlp != null) {
          throw new RuntimeException("double ID");
        }
        this.idMap.put(id, obj);
      }
    }

    parser.require(3, null, elementName);
    return obj;
  }

  public Object readInstance(XmlPullParser parser, String namespace, String name, PropertyInfo expected)
    throws IOException, XmlPullParserException
  {
    Object obj = this.qNameToClass.get(new SoapPrimitive(namespace, name, null));
    if (obj == null) {
      return null;
    }
    if ((obj instanceof Marshal))
      return ((Marshal)obj).readInstance(parser, namespace, name, expected);
    if ((obj instanceof SoapObject))
      obj = ((SoapObject)obj).newInstance();
    else if (obj == SoapObject.class)
      obj = new SoapObject(namespace, name);
    else {
      try {
        obj = ((Class)obj).newInstance();
      } catch (Exception e) {
        throw new RuntimeException(e.toString());
      }
    }

    if ((obj instanceof SoapObject))
      readSerializable(parser, (SoapObject)obj);
    else if ((obj instanceof KvmSerializable))
      readSerializable(parser, (KvmSerializable)obj);
    else if ((obj instanceof Vector))
      readVector(parser, (Vector)obj, expected.elementType);
    else {
      throw new RuntimeException("no deserializer for " + obj.getClass());
    }
    return obj;
  }

  public Object[] getInfo(Object type, Object instance)
  {
    if (type == null) {
      if (((instance instanceof SoapObject)) || ((instance instanceof SoapPrimitive)))
        type = instance;
      else {
        type = instance.getClass();
      }
    }
    if ((type instanceof SoapObject)) {
      SoapObject so = (SoapObject)type;
      return new Object[] { so.getNamespace(), so.getName(), null, null };
    }
    if ((type instanceof SoapPrimitive)) {
      SoapPrimitive sp = (SoapPrimitive)type;
      return new Object[] { sp.getNamespace(), sp.getName(), null, DEFAULT_MARSHAL };
    }
    if (((type instanceof Class)) && (type != PropertyInfo.OBJECT_CLASS)) {
      Object[] tmp = (Object[])(Object[])this.classToQName.get(((Class)type).getName());
      if (tmp != null) {
        return tmp;
      }
    }
    return new Object[] { this.xsd, "anyType", null, null };
  }

  public void addMapping(String namespace, String name, Class clazz, Marshal marshal)
  {
    this.qNameToClass.put(new SoapPrimitive(namespace, name, null), marshal == null ? clazz : marshal);

    this.classToQName.put(clazz.getName(), new Object[] { namespace, name, null, marshal });
  }

  public void addMapping(String namespace, String name, Class clazz)
  {
    addMapping(namespace, name, clazz, null);
  }

  public void addTemplate(SoapObject so)
  {
    this.qNameToClass.put(new SoapPrimitive(so.namespace, so.name, null), so);
  }

  public Object getResponse()
    throws SoapFault
  {
    if ((this.bodyIn instanceof SoapFault)) {
      throw ((SoapFault)this.bodyIn);
    }
    KvmSerializable ks = (KvmSerializable)this.bodyIn;

    if (ks.getPropertyCount() == 0)
      return null;
    if (ks.getPropertyCount() == 1) {
      return ks.getProperty(0);
    }
    Vector ret = new Vector();
    for (int i = 0; i < ks.getPropertyCount(); i++) {
      ret.add(ks.getProperty(i));
    }
    return ret;
  }

  /** @deprecated */
  public Object getResult()
  {
    KvmSerializable ks = (KvmSerializable)this.bodyIn;
    return ks.getPropertyCount() == 0 ? null : ks.getProperty(0);
  }

  public void writeBody(XmlSerializer writer)
    throws IOException
  {
    if (this.bodyOut != null) {
      this.multiRef = new Vector();
      this.multiRef.addElement(this.bodyOut);
      Object[] qName = getInfo(null, this.bodyOut);
      writer.startTag(this.dotNet ? "" : (String)qName[0], (String)qName[1]);
      if (this.dotNet) {
        writer.attribute(null, "xmlns", (String)qName[0]);
      }
      if (this.addAdornments) {
        writer.attribute(null, "id", qName[2] == null ? "o0" : (String)qName[2]);
        writer.attribute(this.enc, "root", "1");
      }
      writeElement(writer, this.bodyOut, null, qName[3]);
      writer.endTag(this.dotNet ? "" : (String)qName[0], (String)qName[1]);
    }
  }

  public void writeObjectBody(XmlSerializer writer, SoapObject obj)
    throws IOException
  {
    SoapObject soapObject = obj;
    int cnt = soapObject.getAttributeCount();
    for (int counter = 0; counter < cnt; counter++) {
      AttributeInfo attributeInfo = new AttributeInfo();
      soapObject.getAttributeInfo(counter, attributeInfo);
      writer.attribute(attributeInfo.getNamespace(), attributeInfo.getName(), attributeInfo.getValue().toString());
    }

    writeObjectBody(writer, (KvmSerializable)obj);
  }

  public void writeObjectBody(XmlSerializer writer, KvmSerializable obj)
    throws IOException
  {
    int cnt = obj.getPropertyCount();
    PropertyInfo propertyInfo = new PropertyInfo();

    for (int i = 0; i < cnt; i++)
    {
      Object prop = obj.getProperty(i);

      obj.getPropertyInfo(i, this.properties, propertyInfo);

      if (!(prop instanceof SoapObject))
      {
        if ((propertyInfo.flags & 0x1) == 0) {
          writer.startTag(propertyInfo.namespace, propertyInfo.name);
          writeProperty(writer, obj.getProperty(i), propertyInfo);
          writer.endTag(propertyInfo.namespace, propertyInfo.name);
        }
      }
      else {
        SoapObject nestedSoap = (SoapObject)prop;

        Object[] qName = getInfo(null, nestedSoap);
        String namespace = (String)qName[0];
        String type = (String)qName[1];
        String name;
        if ((propertyInfo.name != null) && (propertyInfo.name.length() > 0))
          name = propertyInfo.name;
        else {
          name = (String)qName[1];
        }

        writer.startTag(this.dotNet ? "" : namespace, name);
        String prefix = writer.getPrefix(namespace, true);
        writer.attribute(this.xsi, "type", prefix + ":" + type);
        writeObjectBody(writer, nestedSoap);
        writer.endTag(this.dotNet ? "" : namespace, name);
      }
    }
  }

  protected void writeProperty(XmlSerializer writer, Object obj, PropertyInfo type) throws IOException {
    if (obj == null) {
      writer.attribute(this.xsi, this.version >= 120 ? "nil" : "null", "true");
      return;
    }
    Object[] qName = getInfo(null, obj);
    if ((type.multiRef) || (qName[2] != null)) {
      int i = this.multiRef.indexOf(obj);
      if (i == -1) {
        i = this.multiRef.size();
        this.multiRef.addElement(obj);
      }
      writer.attribute(null, "href", "#" + qName[2]);
    } else {
      if ((!this.implicitTypes) || (obj.getClass() != type.type)) {
        String prefix = writer.getPrefix((String)qName[0], true);
        writer.attribute(this.xsi, "type", prefix + ":" + qName[1]);
      }
      writeElement(writer, obj, type, qName[3]);
    }
  }

  private void writeElement(XmlSerializer writer, Object element, PropertyInfo type, Object marshal) throws IOException
  {
    if (marshal != null)
      ((Marshal)marshal).writeInstance(writer, element);
    else if ((element instanceof SoapObject))
      writeObjectBody(writer, (SoapObject)element);
    else if ((element instanceof KvmSerializable))
      writeObjectBody(writer, (KvmSerializable)element);
    else if ((element instanceof Vector))
      writeVectorBody(writer, (Vector)element, type.elementType);
    else
      throw new RuntimeException("Cannot serialize: " + element);
  }

  protected void writeVectorBody(XmlSerializer writer, Vector vector, PropertyInfo elementType)
    throws IOException
  {
    String itemsTagName = "item";
    String itemsNamespace = null;

    if (elementType == null) {
      elementType = PropertyInfo.OBJECT_TYPE;
    } else if (((elementType instanceof PropertyInfo)) && 
      (elementType.name != null)) {
      itemsTagName = elementType.name;
      itemsNamespace = elementType.namespace;
    }

    int cnt = vector.size();
    Object[] arrType = getInfo(elementType.type, null);

    if (!this.implicitTypes) {
      writer.attribute(this.enc, "arrayType", writer.getPrefix((String)arrType[0], false) + ":" + arrType[1] + "[" + cnt + "]");
    }

    boolean skipped = false;
    for (int i = 0; i < cnt; i++)
      if (vector.elementAt(i) == null) {
        skipped = true;
      } else {
        writer.startTag(itemsNamespace, itemsTagName);
        if (skipped) {
          writer.attribute(this.enc, "position", "[" + i + "]");
          skipped = false;
        }
        writeProperty(writer, vector.elementAt(i), elementType);
        writer.endTag(itemsNamespace, itemsTagName);
      }
  }
}